-- $Id: DualSpeccer.lua 30 2014-10-15 17:38:57Z jeffrey0 $
local addon = LibStub("AceAddon-3.0"):NewAddon("DualSpeccer", "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0")
DualSpeccer = addon

local primarySpecSpellName = GetSpellInfo(TALENT_ACTIVATION_SPELLS[1])
local secondarySpecSpellName = GetSpellInfo(TALENT_ACTIVATION_SPELLS[2])

local activeGroup = nil

-- Contains the player-specific info about specializations.
local specCache = {
	{
		groupName = PRIMARY,
		name = "Unknown",
		icon = "Interface\\Icons\\Ability_Marksmanship"
	},
	{
		groupName = SECONDARY,
		name = "Unknown",
		icon = "Interface\\Icons\\Ability_Marksmanship"
	}
}

-- Expose variables for debugging.
DualSpeccer.specCache = specCache

local filtering = false
local lastSwitchStartTime = 0

local rightClickMenu = {
	{
		text = "Hide switch spam",
		func = function() addon.db.profile.filterSpam = not addon.db.profile.filterSpam; addon:UpdateFilter() end,
		checked = function() return addon.db.profile.filterSpam end,
		tooltipTitle = "Hide switch spam",
		tooltipText = "Hides the spell learned/unlearned messages when swapping.",
		isNotRadio = true
	},
	{
		text = "Give custom name",
		func = function() StaticPopup_Show("DualSpeccerCustomName") end,
		tooltipTitle = "Give custom name",
		tooltipText = "Allows you to give a custom name to your specialization.",
		notCheckable = true
	},
}

-- Match patterns we use. Replace any "." with "%." and any "%s" with "(.*)".
local spamFilterMatch1 = string.gsub(ERR_LEARN_ABILITY_S:gsub('%.', '%.'), '%%s', '(.*)')
local spamFilterMatch2 = string.gsub(ERR_LEARN_SPELL_S:gsub('%.', '%.'), '%%s', '(.*)')
local spamFilterMatch3 = string.gsub(ERR_SPELL_UNLEARNED_S:gsub('%.', '%.'), '%%s', '(.*)')
local spamFilterMatch4 = string.gsub(ERR_LEARN_PASSIVE_S:gsub('%.', '%.'), '%%s', '(.*)')

-- Upvalues
local GetTime, GetNumSpecGroups, GetSpecialization, GetActiveSpecGroup =
	GetTime, GetNumSpecGroups, GetSpecialization, GetActiveSpecGroup

local feed = LibStub("LibDataBroker-1.1"):NewDataObject("DualSpeccer", {
	type = "data source",
	text = "DualSpeccer",
	icon = "Interface\\Icons\\Ability_Marksmanship",
	tocname = "DualSpeccer"
})

function addon:OnInitialize()
	local dbDefaults = {
		profile = {
			filterSpam = true
		}
	}
	self.db = LibStub("AceDB-3.0"):New("DualSpeccerDB", dbDefaults, "Default")
	self.db.char.nameOverrides = self.db.char.nameOverrides or {}
	
	StaticPopupDialogs["DualSpeccerCustomName"] = {
		hasEditBox = 1,
		text = "Enter a custom name for your active specialization:",
		button1 = ACCEPT,
		button2 = CANCEL,
		timeout = 0,
		whileDead = 1,
		hideOnEscape = 1,
		maxLetters = 20,
		OnShow = function(self)
			self.editBox:SetText(addon.db.char.nameOverrides[activeGroup] or "")
			self.editBox:SetFocus()
		end,
		OnHide = function(self)
			self.editBox:SetText("")
		end,
		OnAccept = function(self)
			local text = self.editBox:GetText()
			if text == "" then text = nil end
			addon.db.char.nameOverrides[activeGroup] = text
			self:Hide()
			addon:Update()
		end,
		EditBoxOnEnterPressed = function(self)
			local text = self:GetParent().editBox:GetText()
			if text == "" then text = nil end
			addon.db.char.nameOverrides[activeGroup] = text
			self:GetParent():Hide()
			addon:Update()
		end,
		EditBoxOnEscapePressed = function(self)
			self:GetParent():Hide()
		end,
	}
end

function addon:OnEnable()
	self:RegisterEvent("UNIT_SPELLCAST_START")
	self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
	self:RegisterEvent("PLAYER_LEAVING_WORLD")
	
	-- Chances are good that the P_E_W will fire after we're done, registering PLAYER_TALENT_UPDATE twice. This
	-- is more of an AceAddon implementation thing and CallbackHandler overwrites it anyway.
	self:RegisterEvent("PLAYER_ENTERING_WORLD")
	self:RegisterEvent("PLAYER_TALENT_UPDATE")
	
	-- The important part here is that it happens after the first OnUpdate which is seemingly the time when correct
	-- information becomes available.
	self:ScheduleTimer("Update", 0.01)
	
	-- Enable or disable filtering.
	self:UpdateFilter()
end

-- A "corrupted" talent update fires between LEAVING and ENTERING when zoning. We don't want this one.
function addon:PLAYER_LEAVING_WORLD(event)
	self:UnregisterEvent("PLAYER_TALENT_UPDATE")
end

function addon:PLAYER_ENTERING_WORLD(event)
	self:RegisterEvent("PLAYER_TALENT_UPDATE")
end

function addon:ACTIVE_TALENT_GROUP_CHANGED(event, newGroup)
	activeGroup = newGroup
	self:Print(("Switched to %s."):format(specCache[newGroup].pretty))
end

function addon:UNIT_SPELLCAST_START(event, ...)
	local unit, spellName, _, _, spellId = ...
	if unit == "player" and (spellId == TALENT_ACTIVATION_SPELLS[1] or spellId == TALENT_ACTIVATION_SPELLS[2]) then
		lastSwitchStartTime = GetTime()
	end
end

function addon:PLAYER_TALENT_UPDATE(event)
	self:Update()
end

-- Our chat filter function.
local function filter(self, event, msg)
	-- Filtering is only active within 8 seconds of a tree spell cast.
	-- Ideally, this filtering would be done based on events, but most of the spam comes before
	-- the specialization swap events are sent.
	if (lastSwitchStartTime > GetTime() - 8) and (msg:find(spamFilterMatch1) ~= nil or msg:find(spamFilterMatch2) ~= nil or msg:find(spamFilterMatch3) ~= nil or msg:find(spamFilterMatch4) ~= nil) then
		return true
	end
end

local function resolveRoleColor(role)
	if role == "DAMAGER" then
		return "FF8080"
	elseif role == "HEALER" then
		return "80FF80"
	elseif role == "TANK" then
		return "8080FF"
	else
		-- shouldn't happen
		return "ffffff"
	end
end

-- Update the spec information.
function addon:UpdateSpecInfo()
	activeGroup = GetActiveSpecGroup()
	
	-- For both spec groups, update some essential data.
	for specGroup = 1, GetNumSpecGroups() do
		local newSpec = GetSpecialization(false, false, specGroup)
		local _, specName, specIcon, specRole
		if newSpec then
			_, specName, _, specIcon, _, specRole = GetSpecializationInfo(newSpec)
		else
			specName = NONE
			specIcon = "Interface\\Icons\\Ability_Marksmanship"
			specRole = "DAMAGER"
		end
			
		local name = self.db.char.nameOverrides[specGroup] or specName
		specCache[specGroup].name = name
		specCache[specGroup].icon = specIcon
		specCache[specGroup].role = specRole
		local iconString = ("|T%s:0|t"):format(specIcon)
		local specColor = resolveRoleColor(specRole)
		specCache[specGroup].pretty = ("%s|cff%s%s|r"):format(iconString, specColor, specCache[specGroup].name)
	end
end

function addon:UpdateFilter()
	if not filtering and self.db.profile.filterSpam then
		ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", filter)
		filtering = true
	elseif filtering and not self.db.profile.filterSpam then
		ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", filter)
		filtering = false
	end
end

-- Update the addon display and options.
function addon:Update()
	self:UpdateSpecInfo()
	
	-- This should always be sucessful, if not, just let it error.
	local selectedCache = specCache[activeGroup]
	feed.text = selectedCache.name
	feed.icon = selectedCache.icon
end

-- Menu anchoring stuff stolen from ClosetGnome.
local menuPopupFrame = CreateFrame("Frame", "DualSpeccerMenu", UIParent, "UIDropDownMenuTemplate")
function feed.OnClick(displayFrame, button)
	button = button or "LeftButton"
	
	if button == "LeftButton" then
		if GetNumSpecGroups() < 2 then
			addon:Print("This character does not have a second specialization.")
			return
		end

		-- The behavior will need to change if we ever get triple spec.
		local newSpec = activeGroup == 1 and 2 or 1
		SetActiveSpecGroup(newSpec)
	elseif button == "RightButton" then
		EasyMenu(rightClickMenu, menuPopupFrame, displayFrame, 20, 4, "MENU")
	end
end

function feed.OnTooltipShow(tooltip)
	tooltip:AddLine("DualSpeccer")
	local curCache = specCache[activeGroup]
	
	if not curCache then
		addon:Update()
		curCache = specCache[activeGroup]
	end
	
	tooltip:AddDoubleLine("Active", ("%s: %s"):format(curCache.groupName, curCache.pretty))
	
	for i = 1, GetNumSpecGroups() do
		tooltip:AddDoubleLine(specCache[i].groupName, specCache[i].pretty)
	end

	tooltip:AddLine(" ")
	tooltip:AddLine("|cffeda55fClick|r to switch to your other specialization.")	
	tooltip:AddLine("|cffeda55fRight-Click|r to open the menu.")
end

